package org.test4j.module.inject.proxy;

import org.test4j.Context;
import org.test4j.mock.Stubs;
import org.test4j.mock.stub.ProxyInvocation;
import org.test4j.mock.stub.ProxyInvokable;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * 目标对象字段的代理<br>
 * 用于运行时动态获得目标对象字段的实际值调用<br>
 *
 * @author darui.wudr
 */
public class FieldProxy implements ProxyInvokable {
    private final String fieldName;

    private final Field accessor;

    public FieldProxy(final Class target, final String fieldName) {
        this.fieldName = fieldName;
        this.accessor = InjectHelper.getField(target, fieldName);
        this.accessor.setAccessible(true);
    }

    @Override
    public Object invoke(ProxyInvocation invocation) throws Throwable {
        Object fieldValue = accessor.get(Context.currTestObject());
        if (fieldValue == null) {
            throw new NullPointerException(String.format("field[%s] value is null.", fieldName));
        }
        try {
            Method method = invocation.getMethod();
            Object[] paras = invocation.getParametersAsArray();
            boolean accessible = method.isAccessible();
            if (accessible == false) {
                method.setAccessible(true);
            }
            Object o = method.invoke(fieldValue, paras);
            if (accessible == false) {
                method.setAccessible(false);
            }
            return o;
        } catch (Throwable e) {
            if (e instanceof InvocationTargetException) {
                throw ((InvocationTargetException) e).getTargetException();
            } else {
                throw e;
            }
        }
    }

    /**
     * 构造一个代理类，将代理类的操作转移到testedObject属性 (fieldName)对象上<br>
     * 构造一个type类型的mock spring bean
     *
     * @param <T>
     * @param testClazz 测试类
     * @param field     属性
     * @return
     */
    public static <T> T proxy(final Class testClazz, final Field field) {
        FieldProxy handler = new FieldProxy(testClazz, field.getName());
        Class type = field.getType();
        return (T) Stubs.proxy(handler, type);
    }
}